﻿using System.Collections.Generic;

namespace Godot.core.os
{
    using CreateFunc = System.Func<DirAccess>;

    public abstract class DirAccess
    {
        public enum AccessType
        {
            ACCESS_RESOURCES,
            ACCESS_USERDATA,
            ACCESS_FILESYSTEM,
            ACCESS_MAX
        }

        protected string _get_root_path()
        {
            switch(__access_type) 
            {
		        case AccessType.ACCESS_RESOURCES: return Globals.singleton.get_resource_path();
		        case AccessType.ACCESS_USERDATA: return OS.singleton.get_data_dir();
		        default: return "";
	        }
        }

        protected string _get_root_string()
        {
            switch (__access_type)
            {
                case AccessType.ACCESS_RESOURCES: return "res://";
                case AccessType.ACCESS_USERDATA: return "user://";
                default: return "";
            }
        }

        protected string _fix_path(string p_path)
        {
            //helper used by file accesses that use a single filesystem
            string r_path = p_path.Replace("\\", "/");

            switch (__access_type)
            {
                case AccessType.ACCESS_RESOURCES:
                    {
                        if (Globals.singleton != null)
                        {
                            if (r_path.StartsWith("res://"))
                            {
                                string resource_path = Globals.singleton.get_resource_path();
                                if (resource_path != "")
                                {
                                    return r_path.Replace("res:/", resource_path);
                                };
                                return r_path.Replace("res://", "");
                            }
                        }
                    } break;

                case AccessType.ACCESS_USERDATA:
                    {
                        if (r_path.StartsWith("user://"))
                        {
                            string data_dir = OS.singleton.get_data_dir();
                            if (data_dir != "")
                            {
                                return r_path.Replace("user:/", data_dir);
                            };
                            return r_path.Replace("user://", "");
                        }

                    } break;

                case AccessType.ACCESS_FILESYSTEM:
                    {
                        return r_path;
                    } break;
            }

            return r_path;
        }

        protected Error _erase_recursive()
        {
            List<string> dirs = new List<string>();
	        List<string> files = new List<string>();

	        list_dir_begin();
	        string n = get_next();
	        while(n != "") 
            {
		        if (n != "." && n != "..") 
                {

			        if (current_is_dir())
				        dirs.Add(n);
			        else
				        files.Add(n);
		        }

		        n = get_next();
	        }

	        list_dir_end();
 
            foreach(string E in dirs)
            {
		        Error err = change_dir(E);
		        if (err == Error.OK) 
                {
			        err = _erase_recursive();
			        if (err != Error.OK) 
                    {
				        //print_line("err recurso "+E->get());
				        return err;
			        }
			        
                    err = change_dir("..");
			        if (err != Error.OK) 
                    {
				        //print_line("no go back "+E->get());
				        return err;
			        }
			        
                    err = remove(get_current_dir().plus_file(E));
			        if (err != Error.OK) 
                    {
				        //print_line("no remove dir"+E->get());
				        return err;
			        }
		        } 
                else 
                {
			        //print_line("no change to "+E->get());
			        return err;
		        }
	        }
 
            foreach(string E in files)
            {

		        Error err = remove(get_current_dir().plus_file(E));
		        if (err != Error.OK) 
                {
			        //print_line("no remove file"+E->get());
			        return err;
		        }
	        }

	        return Error.OK;
        }

        protected static DirAccess _create_builtin<T>() where T : DirAccess, new()
        {
            return new T();
        }

        public static string normalize_path(string p_path)
        {
            int max_depth = 64;
	        int[] pos_stack = new int[max_depth];
	        int curr = 0;

	        int pos = 0;
	        string cur_dir = "";

	        do 
            {
		        if (curr >= max_depth) 
                {
			        throw new System.Exception("Directory depth too deep.");
			        return "";
		        };

		        int start = pos;

		        int next = p_path.IndexOf("/", pos);
		        if (next < 0) 
                {
			        next = p_path.Length - 1;
		        }

		        pos = next + 1;

		        cur_dir = p_path.Substring(start, next - start);

		        if (cur_dir == "" || cur_dir == ".") 
                {
			        continue;
		        }

		        if (cur_dir == "..") {

                    if (curr > 0)// pop a dir 
                    { 
				        curr -= 2;
			        };
			        continue;
		        };

		        pos_stack[curr++] = start;
		        pos_stack[curr++] = next;

	        } while (pos < p_path.Length);

	        string path = "";
	        if (p_path[0] == '/') 
            {
		        path = "/";
	        }

	        int i = 0;
	        while (i < curr) 
            {
		        int start = pos_stack[i++];

		        while ( ((i+1) < curr) && (pos_stack[i] == pos_stack[i+1]) ) 
                {
			        ++i;
		        };
		        path = path + p_path.Substring(start, (pos_stack[i++] - start) + 1);
	        };
	        return path;
        }

        public abstract bool list_dir_begin();

        public abstract string get_next();

        public virtual string get_next(ref bool p_is_dir)
        {
            string next = get_next();
            p_is_dir = current_is_dir();
            return next;
        }

        public abstract bool current_is_dir();

        public abstract bool list_dir_end();

        public abstract int get_drive_count();

        public abstract string get_drive(int p_drive);

        public abstract Error change_dir(string p_dir);

        public abstract string get_current_dir();

        public abstract Error make_dir(string p_dir);

        public virtual Error make_dir_recursive(string p_dir)
        {
            if (p_dir.Length < 1) 
            {
		        return Error.OK;
	        }

	        string cur = normalize_path(Globals.singleton.globalize_path(get_current_dir()));
	        
            if (cur[cur.Length - 1] != '/') 
            {
		        cur = cur + "/";
	        };

	        string dir = normalize_path(Globals.singleton.globalize_path(p_dir));
	        if (dir.Length < 1) 
            {
		        return Error.OK;
	        }

	        if (dir[dir.Length - 1] != '/') 
            {
		        dir = dir + "/";
	        }

	        if(dir.IndexOf(cur) != 0)
                return Error.FAILED;

	        string rel = dir.Substring(cur.Length, (dir.Length - cur.Length));

	        int pos = 0;
	        while (pos < rel.Length) 
            {
		        int n = rel.IndexOf("/", pos);
		        if (n < 0) 
                {
			        n = rel.Length;
		        }
		        
                pos = n + 1;

		        if (pos > 1) 
                {
			        Error err = make_dir(rel.Substring(0, pos -1));
			        if (err != Error.OK && err != Error.ERR_ALREADY_EXISTS) 
                    {
				        return err;
			        }
		        };
	        };

	        return Error.OK;
        }

        public virtual Error erase_contents_recursive()
        {
            return _erase_recursive();
        }

        public abstract bool file_exists(string p_file);

        public abstract uint get_space_left();

        public virtual Error copy(string p_from, string p_to)
        {
            //printf("copy %s -> %s\n",p_from.ascii().get_data(),p_to.ascii().get_data());
	        Error err = Error.FAILED;
	        FileAccess fsrc = FileAccess.open(p_from, FileAccess.ModeFlags.READ, ref err);
	
	        if (err != Error.OK) 
            {
		        return err;
	        }
	
	        FileAccess fdst = FileAccess.open(p_to, FileAccess.ModeFlags.WRITE,ref err );
	        if (err != Error.OK) 
            {				
		        fsrc.close();
                return err;
	        }
	
	        fsrc.seek_end(0);
	        uint size = fsrc.get_pos();
	        fsrc.seek(0);
	        err = Error.OK;
	        while((size--) != 0) 
            {	
		        if (fsrc.get_error() != Error.OK) 
                {
			        err= fsrc.get_error();
			        break;
		        }

		        if (fdst.get_error() != Error.OK) 
                {
			        err= fdst.get_error();
			        break;
		        }
		
		        fdst.store_8( fsrc.get_8() );
	        }

            fsrc.close();
            fdst.close();
	
	        return err;
        }

        public abstract Error rename(string p_from, string p_to);
	
        public abstract Error remove(string p_name);

        public static string get_full_path(string p_path, AccessType p_access)
        {
            DirAccess d = DirAccess.create(p_access);
	        if (d == null)
		        return p_path;
		
	        d.change_dir(p_path);
	        string full = d.get_current_dir();
	        return full;
        }

        public static DirAccess create_for_path(string p_path)
        {
            DirAccess da = null;
            if (p_path.StartsWith("res://"))
            {
                da = create(AccessType.ACCESS_RESOURCES);
            }
            else if (p_path.StartsWith("user://"))
            {

                da = create(AccessType.ACCESS_USERDATA);
            }
            else
            {
                da = create(AccessType.ACCESS_FILESYSTEM);
            }

            return da;
        }

        public static DirAccess create(AccessType p_access)
        {
            DirAccess da = (__create_func[(int)p_access] != null) ? __create_func[(int)p_access]() : null;
            if (da != null)
            {
                da.__access_type = p_access;
            }
            return da;
        }

        public static DirAccess open(string p_path, ref Error r_error) 
        {
	        DirAccess da = create_for_path(p_path);

	        if(da == null) return null;

	        Error err = da.change_dir(p_path);
	        r_error = err;
	        
            if (err != Error.OK) 
            {
		        return null;
	        }
	        return da;
        }

        public static void make_default<T>(AccessType p_access) where T : DirAccess, new()
        {
            __create_func[(int)p_access] = _create_builtin<T>;
        }

        protected bool _next_is_dir;

        private AccessType __access_type;

        private static CreateFunc[] __create_func = new CreateFunc[(int)AccessType.ACCESS_MAX];
    }
}
